package com.douya.security.filter;

import cn.hutool.json.JSONUtil;
import com.douya.common.enums.CommonResultEnums;
import com.douya.common.ex.ApiException;
import com.douya.common.pojo.vo.CommonResponseVO;
import com.douya.security.utils.JwtUtils;
import com.douya.security.property.DouYaSecurityProperties;
import com.douya.security.property.HeaderTokenProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.server.ResponseStatusException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.AccessDeniedException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * jwt token过滤器
 *
 * @author hcy
 * @date 2019-11-28 15:52
 */
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    UserDetailsService userDetailsService;

    DouYaSecurityProperties mSecurityProperties;

    RedissonClient mRedissonClient;

    JwtUtils jwtUtil;

    public JwtAuthenticationTokenFilter(UserDetailsService userDetailsService,
                                        DouYaSecurityProperties mSecurityProperties,
                                        RedissonClient mRedissonClient,
                                        JwtUtils jwtUtil) {
        this.userDetailsService = userDetailsService;
        this.mSecurityProperties = mSecurityProperties;
        this.mRedissonClient = mRedissonClient;
        this.jwtUtil = jwtUtil;
    }


    @SuppressWarnings("NullableProblems")
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        String id;
        boolean debug = mSecurityProperties.isDebug();
        HeaderTokenProperties headerToken = mSecurityProperties.getHeaderToken();
        try {
            if (debug) {
                id = request.getHeader(headerToken.getName());
            } else {
                String token = request.getHeader(headerToken.getName());
                if (StringUtils.isBlank(token)) {
                    sendErrorResponse(response, CommonResultEnums.UNAUTHORIZED);
                    return;
                }
                boolean multiLogin = mSecurityProperties.isMultiLogin();
                String key;
                RBucket<Object> bucket;
                if (multiLogin) {
                    key = headerToken.getTokenPrefix() + token;
                    bucket = mRedissonClient.getBucket(key);
                    String tokenValue = bucket.isExists() ? (String) bucket.get() : "";
                    if (StringUtils.isBlank(tokenValue)) {
                        sendErrorResponse(response, CommonResultEnums.UNAUTHORIZED);
                        return;
                    }
                    id = tokenValue;
                } else {
                    String userId = jwtUtil.getUsernameFromToken(token);
                    key = headerToken.getTokenPrefix() + userId;
                    bucket = mRedissonClient.getBucket(key);
                    String tokenValue = bucket.isExists() ? (String) bucket.get() : "";
                    if (!StringUtils.equals(token, tokenValue)) {
                        sendErrorResponse(response, CommonResultEnums.OTHER_LOGIN);
                        return;
                    }
                    id = userId;
                }
                long expire = headerToken.getExpireTime().toMinutes();
                bucket.expire(expire, TimeUnit.MINUTES);
            }
            if (StringUtils.isNotBlank(id)) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(id);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } else {
                sendErrorResponse(response, CommonResultEnums.UNAUTHORIZED);
                return;
            }
            chain.doFilter(request, response);
        } catch (Exception e) {
            log.error("JwtAuthenticationTokenFilter error", e);
            sendErrorResponse(response, CommonResultEnums.UNAUTHORIZED);
        }
    }

    /**
     * 发送标准的 JSON 错误响应
     *
     * @param response         HttpServletResponse
     * @param commonResultEnum 状态
     */
    public static void sendErrorResponse(HttpServletResponse response, CommonResultEnums commonResultEnum) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        // 构造 JSON 错误信息
        try {
            response.getWriter().write(JSONUtil.toJsonStr(CommonResponseVO.error(commonResultEnum)));
            // 强制刷新响应
            response.flushBuffer();
        } catch (IOException e) {
            log.error("sendErrorResponse error", e);
        }
    }


}
